package utils.binary;

import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import utils.exception.RuntimeIOException;

import java.io.UnsupportedEncodingException;

/**
 * <h1>字节操作相关的工具</h1>
 * <p>该类包括了字节和各种数据类型的互相转化</p>
 * <p>支持的基本数据类型包括：int, short, long, char, string, boolean</p>
 * <p>
 *     <b>int 与 byte 的互转</b>
 *     <ul>
 *         <li>
 *             int to bytes
 *             <ul>
 *                 <li>{@link #toBytes(int)}</li>
 *                 <li>{@link #toBytes(int, byte[])}</li>
 *                 <li>{@link #toBytes(int, byte[], int)}</li>
 *                 <li>{@link #toBytesBigEndian(int, byte[], int)}</li>
 *                 <li>{@link #toBytesLittleEndian(int, byte[], int)}</li>
 *             </ul>
 *         </li>
 *         <li>
 *             bytes to int
 *             <ul>
 *                 <li>{@link #toInt(byte[])}</li>
 *                 <li>{@link #toInt(byte[], int)}</li>
 *                 <li>{@link #toInt(byte[], int, int)}</li>
 *                 <li>{@link #toInt(byte[], int, int, boolean)}</li>
 *             </ul>
 *         </li>
 *     </ul>
 * </p>
 * <p>
 *     <b>short 与 byte 的互转</b>
 *     <ul>
 *         <li>
 *             short to bytes
 *             <ul>
 *                 <li>{@link #toBytes(short)}</li>
 *                 <li>{@link #toBytes(short, byte[])}</li>
 *                 <li>{@link #toBytes(short, byte[], int)}</li>
 *             </ul>
 *         </li>
 *         <li>
 *             bytes to short
 *             <ul>
 *                 <li>{@link #toShort(byte[])}</li>
 *                 <li>{@link #toShort(byte[], int)}</li>
 *             </ul>
 *         </li>
 *     </ul>
 * </p>
 * <p>
 *     <b>long 与 byte 的互转</b>
 *     <ul>
 *         <li>
 *             long to bytes
 *             <ul>
 *                 <li>{@link #toBytes(long)}</li>
 *                 <li>{@link #toBytes(long, byte[], int)}</li>
 *             </ul>
 *         </li>
 *         <li>
 *             bytes to long
 *             <ul>
 *                 <li>{@link #toLong(byte[])}</li>
 *                 <li>{@link #toLong(byte[], int)}</li>
 *             </ul>
 *         </li>
 *     </ul>
 * </p>
 * <p>
 *     <b>char 与 byte 的互转</b>
 *     <ul>
 *         <li>
 *             char to bytes: {@link #toBytes(char, byte[], int)}
 *         </li>
 *         <li>
 *             bytes to char: {@link #toChar(byte[], int)}
 *         </li>
 *     </ul>
 * </p>
 * <p>
 *     <b>string 与 byte 的互转</b><br>
 *     注：默认编码格式为 UTF-8（{@link #DEFAULT_CHARSET}）
 *     <ul>
 *         <li>
 *             string to bytes
 *             <ul>
 *                 <li>{@link #toBytes(String)}</li>
 *                 <li>{@link #toBytes(String, String)}</li>
 *             </ul>
 *         </li>
 *         <li>
 *             bytes to string
 *             <ul>
 *                 <li>{@link #toString(byte[])}</li>
 *                 <li>{@link #toString(byte[], int)}</li>
 *                 <li>{@link #toString(byte[], String)}</li>
 *                 <li>{@link #toString(byte[], int, int)}</li>
 *                 <li>{@link #toString(byte[], int, int, String)}</li>
 *             </ul>
 *         </li>
 *     </ul>
 * </p>
 * <p>
 *     <b>boolean 与 byte 的互转</b><br>
 *     注：若布尔值为 true，则字节为 1（{@link #TRUE_BYTE}）；若布尔值为 false，则字节为 false（{@link #FALSE_BYTE}）
 *     <ul>
 *         <li>
 *             boolean to byte: {@link #toBytes(boolean)}
 *         </li>
 *         <li>
 *             byte to boolean: {@link #toBoolean(byte)}
 *         </li>
 *     </ul>
 * </p>
 * <p></p>
 *
 * @author Zhou Yifan
 */
public class BytesUtils {

    /**
     * 默认编码格式：UTF-8
     */
    public static final String DEFAULT_CHARSET = "UTF-8";

    /**
     * 空字节数组
     */
    public static final byte[] EMPTY_BYTES = {};

    /**
     * 最大缓存大小
     */
    public static final int MAX_BUFFER_SIZE = 1024 * 1024 * 1024;

    /**
     * 缓存大小
     */
    public static final int BUFFER_SIZE = 64;

    /**
     * 布尔值为 true 的字节
     */
    public static final byte TRUE_BYTE = 1;

    /**
     * 布尔值为 false 的字节
     */
    public static final byte FALSE_BYTE = 0;

    private BytesUtils() {
    }

    /*----------------*/
    /* toBytes(value) */
    /*----------------*/

    /**
     * 将 int 值转为 4 字节的二进制数组
     * <p>
     * 以“高位在前”的方式转换，即：数值的高位保存在数组地址的低位（Big Endian 模式）
     *
     * @param value 要转换的int整数
     */
    public static byte @NotNull [] toBytes(int value) {
        byte[] bytes = new byte[4];
        toBytes(value, bytes, 0);
        return bytes;
    }

    /**
     * 将 short 值转为 2 字节的二进制数组
     *
     * @param value 待转换的 short 整数
     * @return 转换后的字节数组
     */
    public static byte @NotNull [] toBytes(short value) {
        byte[] bytes = new byte[2];
        toBytes(value, bytes, 0);
        return bytes;
    }

    /**
     * 将 boolean 值转为 1 比特的二进制数组
     *
     * @param value 待转换的 boolean
     * @return 转换后的字节数组
     */
    @Contract(value = "_ -> new", pure = true)
    public static byte @NotNull [] toBytes(boolean value) {
        return new byte[]{value ? TRUE_BYTE : FALSE_BYTE};
    }

    /**
     * 将 long 值转为 8 字节的二进制数组
     *
     * @param value 待转换的 long 整数
     * @return 转换后的字节数组
     */
    @Contract(pure = true)
    public static byte @NotNull [] toBytes(long value) {
        byte[] bytes = new byte[8];
        toBytes(value, bytes, 0);
        return bytes;
    }

    /**
     * 将 String 转为字节数组
     *
     * @param str 待转换的字符串
     * @return 转换后的字节数组
     */
    public static byte[] toBytes(String str) {
        return toBytes(str, DEFAULT_CHARSET);
    }

    /**
     * 将 String 转为字节数组
     *
     * @param str     待转换的字符串
     * @param charset 编码
     * @return 转换后的字节数组，如果待转换字符串是 null，那么转换结果也是 null
     */
    public static byte @Nullable [] toBytes(String str, String charset) {
        if (null == str) {
            return null;
        }
        try {
            return str.getBytes(charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeIOException(e.getMessage(), e);
        }
    }

    /* toBytes(value,bytes) */

    /**
     * 将 short 值转为 2 字节的二进制数组
     *
     * @param value 要转换的 short 整数
     * @param bytes 要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 0 开始的 2 个元素
     */
    public static void toBytes(short value, byte[] bytes) {
        toBytes(value, bytes, 0);
    }

    /**
     * 将 int 值转为 4 字节的二进制数组
     *
     * @param value 要转换的int整数
     * @param bytes 要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 0 开始的4个元素
     */
    public static int toBytes(int value, byte[] bytes) {
        return toBytes(value, bytes, 0);
    }


    /*-----------------------------*/
    /* toBytes(value,bytes,offset) */
    /*-----------------------------*/

    /**
     * 将 int 值转为 4 字节的二进制数组
     * <p>
     * 以“高位在前”的方式转换，即：数值的高位保存在数组地址的低位（Big Endian 模式）
     *
     * @param value  要转换的int整数
     * @param bytes  要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的4个元素
     * @param offset 写入转换结果的起始位置
     * @return 返回写入的长度
     */
    public static int toBytes(int value, byte[] bytes, int offset) {
        return toBytesBigEndian(value, bytes, offset);
    }

    /**
     * 将 int 值转为 4 字节的二进制数组
     * <p>
     * 以“高位在前”的方式转换，即：数值的高位保存在数组地址的低位（Big Endian 模式）
     *
     * @param value  要转换的int整数
     * @param bytes  要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的4个元素
     * @param offset 写入转换结果的起始位置
     * @return 返回写入的长度
     */
    public static int toBytesBigEndian(int value, byte @NotNull [] bytes, int offset) {
        bytes[offset] = (byte) ((value >>> 24) & 0x00FF);
        bytes[offset + 1] = (byte) ((value >>> 16) & 0x00FF);
        bytes[offset + 2] = (byte) ((value >>> 8) & 0x00FF);
        bytes[offset + 3] = (byte) (value & 0x00FF);
        return 4;
    }

    /**
     * 将 int 值转为 4 字节的二进制数组
     * <p>
     * 以“高位在后”的方式转换，即：数值的高位保存在数组地址的高位（Little Endian 模式）
     *
     * @param value  要转换的int整数
     * @param bytes  要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的4个元素
     * @param offset 写入转换结果的起始位置
     * @return 返回写入的长度
     */
    public static int toBytesLittleEndian(int value, byte @NotNull [] bytes, int offset) {
        bytes[offset] = (byte) (value & 0x00FF);
        bytes[offset + 1] = (byte) ((value >>> 8) & 0x00FF);
        bytes[offset + 2] = (byte) ((value >>> 16) & 0x00FF);
        bytes[offset + 3] = (byte) ((value >>> 24) & 0x00FF);
        return 4;
    }

    /**
     * 将 short 值转为 2 字节的二进制数组
     * <p>
     * 以“高位在后”的方式转换，即：数值的高位保存在数组地址的高位（Little Endian 模式）
     *
     * @param value  要转换的int整数
     * @param bytes  要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的4个元素
     * @param offset 写入转换结果的起始位置
     */
    public static void toBytes(short value, byte @NotNull [] bytes, int offset) {
        bytes[offset] = (byte) ((value >>> 8) & 0x00FF);
        bytes[offset + 1] = (byte) (value & 0x00FF);
    }

    /**
     * 将 char 值转为 2 字节的二进制数组
     * <p>
     * 以“高位在后”的方式转换，即：数值的高位保存在数组地址的高位（Little Endian 模式）
     *
     * @param value  要转换的 char 字符
     * @param bytes  要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的 1 个元素
     * @param offset 写入转换结果的起始位置
     */
    public static void toBytes(char value, byte @NotNull [] bytes, int offset) {
        bytes[offset] = (byte) ((value >>> 8) & 0x00FF);
        bytes[offset + 1] = (byte) (value & 0x00FF);
    }

    /**
     * 将 long 值转为 8 字节的二进制数组，无需返回值，写入长度必然为 8
     *
     * @param value  待转换的 long 整数
     * @param bytes  要保存转换结果的二进制数组转换结果将从高位至低位的顺序写入数组从 offset 指定位置开始的 8 个元素
     * @param offset 写入转换结果的起始位置
     */
    public static void toBytes(long value, byte @NotNull [] bytes, int offset) {
        // long 类型占 8 个字节，一个字节为 8 比特
        // 例如 byte = 1001 1100，为了方便，以下一个字节用 : 分开
        // 0x00FF = 0000 0000 : 1111 1111
        // & 0x00FF 的作用是取得高 8 位，为了便于理解，举个例子
        // long x = 1110 1101 : 1011 0111 : 1110 1101 : 1011 0111 : 1110 1101 : 1011 0111 : 1110 1101 : 1011 0111
        // y = x >>> 48 = 0000 0000 : 0000 0000 : 0000 0000 : 0000 0000 : 0000 0000 : 0000 0000 : 1001 0111 : 1011 0111
        // y & 0000 0000 : 0000 0000 : 0000 0000 : 0000 0000 : 0000 0000 : 0000 0000 : 0000 0000 : 1011 0111
        // 转换成 byte 就是 1011 0111 了
        bytes[offset] = (byte) ((value >>> 56) & 0x00FF);
        bytes[offset + 1] = (byte) ((value >>> 48) & 0x00FF);
        bytes[offset + 2] = (byte) ((value >>> 40) & 0x00FF);
        bytes[offset + 3] = (byte) ((value >>> 32) & 0x00FF);
        bytes[offset + 4] = (byte) ((value >>> 24) & 0x00FF);
        bytes[offset + 5] = (byte) ((value >>> 16) & 0x00FF);
        bytes[offset + 6] = (byte) ((value >>> 8) & 0x00FF);
        bytes[offset + 7] = (byte) (value & 0x00FF);
    }

    /*-------*/
    /* toInt */
    /*-------*/

    /**
     * 按从高位到低位的顺序将指定二进制数组从位置 0 开始的 4 个字节转换为 int 整数
     *
     * @param bytes 要转换的二进制数组
     * @return 转换后的 int 整数
     */
    public static int toInt(byte @NotNull [] bytes) {
        int length = bytes.length;
        if (length < 4) {
            byte[] buffer = new byte[4];
            System.arraycopy(bytes, 0, buffer, buffer.length - length, length);
            return toInt(buffer, 0);
        } else {
            return toInt(bytes, 0);
        }
    }

    /**
     * 按从高位到低位的顺序将指定二进制数组从 offset 参数指定的位置开始的 4 个字节转换为 int 整数
     *
     * @param bytes  要转换的二进制数组
     * @param offset 要读取数据的开始位置
     * @return 转换后的 int 整数
     */
    public static int toInt(byte[] bytes, int offset) {
        return toInt(bytes, offset, 4);
    }

    /**
     * 按从高位到低位的顺序将指定二进制数组从 offset 参数指定的位置开始的 4 个字节转换为 int 整数
     *
     * @param bytes  要转换的二进制数组
     * @param offset 要读取数据的开始位置
     * @param len    长度len 必须满足： len 大于等于 1 且小于等于4
     * @return 转换后的 int 整数
     */
    public static int toInt(byte[] bytes, int offset, int len) {
        return toInt(bytes, offset, len, true);
    }

    /**
     * 按从高位到低位的顺序将指定二进制数组从 offset 参数指定的位置开始的 4 个字节转换为 int 整数
     *
     * @param bytes     要转换的二进制数组
     * @param offset    要读取数据的开始位置
     * @param len       长度len 必须满足： len 大于等于 1 且小于等于4
     * @param highAlign 是否高位对齐<br>
     *                  true 表示参数 bytes 的首个字节对应为整数的最高8位<br>
     *                  false 表示参数 bytes 的最后字节对应为整数的最低8位
     * @return 转换后的 int 整数
     */
    public static int toInt(byte[] bytes, int offset, int len, boolean highAlign) {
        if (len < 1 || len > 4) {
            throw new IllegalArgumentException("Len less than 1 or greater than 4!");
        }
        int value = 0;
        if (highAlign) {
            for (int i = 0; i < len; i++) {
                value = value | ((bytes[offset + i] & 0xFF) << (8 * (3 - i)));
            }
        } else {
            for (int i = 0; i < len; i++) {
                value = value | ((bytes[offset + i] & 0xFF) << (8 * (len - 1 - i)));
            }
        }

        return value;
    }

    /* toShort */

    /**
     * 按从高位到低位的顺序将指定二进制数组从 offset 参数指定的位置开始的 2 个字节转换为 short 整数
     *
     * @param bytes  要转换的二进制数组
     * @param offset 要读取数据的开始位置
     * @return 转换后的 short 整数
     */
    @Contract(pure = true)
    public static short toShort(byte @NotNull [] bytes, int offset) {
        short value = 0;
        value = (short) ((value | (bytes[offset] & 0xFF)) << 8);
        value = (short) (value | (bytes[offset + 1] & 0xFF));

        return value;
    }

    /**
     * 按从高位到低位的顺序将指定二进制数组从 0 位置开始的 2 个字节转换为 short 整数
     *
     * @param bytes 要转换的二进制数组
     * @return 转换后的 short 整数
     */
    public static short toShort(byte[] bytes) {
        return toShort(bytes, 0);
    }


    /*--------*/
    /* toLong */
    /*--------*/

    /**
     * 按从高位到低位的顺序将一个 8 字节二进制数组转成 long 值（也允许大于 8 字节，但是读取起始位置为 0）
     *
     * @param bytes 8 字节的二进制数组
     * @return 转换后的 long 值
     */
    public static long toLong(byte[] bytes) {
        return toLong(bytes, 0);
    }

    /**
     * 按从高位到低位的顺序将 byte 数组（读取 8 位）转换为 long 值
     *
     * @param bytes  二进制数组
     * @param offset 要读取数据的开始位置
     * @return 转换后的 long 数组
     */
    @Contract(pure = true)
    public static long toLong(byte @NotNull [] bytes, int offset) {
        // toBytes 的逆运算，无需多言
        long value = 0;
        value = (value | (bytes[offset] & 0xFF)) << 8;
        value = (value | (bytes[offset + 1] & 0xFF)) << 8;
        value = (value | (bytes[offset + 2] & 0xFF)) << 8;
        value = (value | (bytes[offset + 3] & 0xFF)) << 8;
        value = (value | (bytes[offset + 4] & 0xFF)) << 8;
        value = (value | (bytes[offset + 5] & 0xFF)) << 8;
        value = (value | (bytes[offset + 6] & 0xFF)) << 8;
        value = value | (bytes[offset + 7] & 0xFF);

        return value;
    }

    /*----------*/
    /* toString */
    /*----------*/

    /**
     * 以默认字符集({@value #DEFAULT_CHARSET})生成字符串；
     *
     * @param bytes 二进制数组
     * @return 字符串
     */
    public static String toString(byte[] bytes) {
        return toString(bytes, DEFAULT_CHARSET);
    }

    /**
     * 以默认字符集({@value #DEFAULT_CHARSET})生成字符串；
     *
     * @param bytes  二进制数组
     * @param offset 起始位置
     * @return 字符串
     */
    public static String toString(byte[] bytes, int offset) {
        return toString(bytes, offset, bytes.length - offset, DEFAULT_CHARSET);
    }

    /**
     * 以默认字符集({@value #DEFAULT_CHARSET})生成字符串；
     *
     * @param bytes  二进制数组
     * @param offset 起始位置
     * @param len    长度
     * @return 字符串
     */
    public static String toString(byte[] bytes, int offset, int len) {
        return toString(bytes, offset, len, DEFAULT_CHARSET);
    }

    /**
     * 以指定字符集生成字符串；
     *
     * @param bytes   二进制数组
     * @param charset 字符集
     * @return 字符串
     */
    public static String toString(byte[] bytes, String charset) {
        return toString(bytes, 0, bytes.length, charset);
    }

    /**
     * 以指定字符集生成字符串；
     *
     * @param bytes   二进制数组
     * @param offset  起始位置
     * @param len     长度
     * @param charset 字符集
     * @return 字符串
     */
    public static @Nullable String toString(byte[] bytes, int offset, int len, String charset) {
        try {
            if (bytes == null) {
                return null;
            }
            if (len == 0) {
                return "";
            }
            return new String(bytes, offset, len, charset);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeIOException(e.getMessage(), e);
        }
    }

    /*-----------*/
    /* toBoolean */
    /*-----------*/

    /**
     * 将字节转换成布尔值
     *
     * @param value 二进制数
     * @return 布尔值
     */
    public static boolean toBoolean(byte value) {
        return value != FALSE_BYTE;
    }

    /*--------*/
    /* toChar */
    /*--------*/

    /**
     * 将字节数组转换成 char
     *
     * @param bytes  字节数组
     * @param offset 起始位置
     * @return char 值
     */
    @Contract(pure = true)
    public static char toChar(byte @NotNull [] bytes, int offset) {
        char value = 0;
        value = (char) ((value | (bytes[offset] & 0xFF)) << 8);
        value = (char) (value | (bytes[offset + 1] & 0xFF));

        return value;
    }

}
